//
//  SLCATimerAnimationController.m
//  Objective-C
//
//  Created by 陆承东 on 2018/7/3.
//  Copyright © 2018年 S_Line. All rights reserved.
//

#import "SLCATimerAnimationController.h"

CGFloat const containerViewWH = 300.0;
CGFloat const ballImageViewWH = 20.0;

@interface SLCATimerAnimationController ()

@property (nonatomic, weak) UIView *containerView;
@property (nonatomic, weak) UIImageView *ballImageView;
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic, assign) NSTimeInterval duration;
@property (nonatomic, assign) NSTimeInterval timeOffset;
@property (nonatomic, assign) NSTimeInterval lastStep;
@property (nonatomic, strong) id fromValue;
@property (nonatomic, strong) id toValue;

@end

@implementation SLCATimerAnimationController

- (void)initNavBar {
    self.navTitle = @"TimerAnimation 定时帧";
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self initNavBar];
    
    [self initView];
}

- (void)initView {
    UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, containerViewWH, containerViewWH)];
    self.containerView = containerView;
    containerView.backgroundColor = [UIColor whiteColor];
    containerView.center = CGPointMake(kScreenWidth / 2.0, kNavigationBarHeight + containerViewWH / 2.0 + 20);
    [self.view addSubview:containerView];
    
    UIImageView *ballImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"caBall"]];
    self.ballImageView = ballImageView;
    ballImageView.frame = CGRectMake(0, 0, ballImageViewWH, ballImageViewWH);
    ballImageView.center = CGPointMake(containerViewWH / 2.0, ballImageViewWH / 2.0);
    [containerView addSubview:ballImageView];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self animate];
}

float interpolate1(float from, float to, float time) {
    return (to - from) * time + from;
}

- (id)interpolateFromValue:(id)fromValue toValue:(id)toValue time:(float)time {
    if ([fromValue isKindOfClass:[NSValue class]]) {
        // get type
        const char *type = [(NSValue *)fromValue objCType];
        if (strcmp(type, @encode(CGPoint)) == 0) {
            CGPoint from = [fromValue CGPointValue];
            CGPoint to = [toValue CGPointValue];
            CGPoint result = CGPointMake(interpolate1(from.x, to.x, time), interpolate1(from.y, to.y, time));
            return [NSValue valueWithCGPoint:result];
        }
    }
    
    // provide safe default implementation
    return (time < 0.5) ? fromValue : toValue;
}

float bounceEaseOut1(float t) {
    if (t < 4/11.0) {
        return (121 * t * t)/16.0;
    } else if (t < 8/11.0) {
        return (363/40.0 * t * t) - (99/10.0 * t) + 17/5.0;
    } else if (t < 9/10.0) {
        return (4356/361.0 * t * t) - (35442/1805.0 * t) + 16061/1805.0;
    }
    return (54/5.0 * t * t) - (513/25.0 * t) + 268/25.0;
}

- (void)animate {
    // reset ball to top of containerView
    self.ballImageView.center = CGPointMake(containerViewWH / 2.0, ballImageViewWH / 2.0);
    
    // configure the animation
    self.duration = 1.2;
    self.timeOffset = 0.0;
    self.fromValue = [NSValue valueWithCGPoint:CGPointMake(containerViewWH / 2.0, ballImageViewWH / 2.0)];
    self.toValue = [NSValue valueWithCGPoint:CGPointMake(containerViewWH / 2.0, containerViewWH - ballImageViewWH / 2.0)];
    // stop the timer if it's already running
    [self.timer invalidate];
    // start the timer
    // NSTimer
//    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 / 60.0 target:self selector:@selector(step:) userInfo:nil repeats:true];
    // CADisplayLink
    self.lastStep = CACurrentMediaTime();
    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(step:)];
    [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}

- (void)step:(NSTimer *)timer {
    // NSTimer
//    // update time offset
//    self.timeOffset = MIN(self.timeOffset + 1.0 / 60.0, self.duration);
//    // get normalized time offset (in range 0 - 1)
//    float time = self.timeOffset / self.duration;
//    // apply easing
//    time = bounceEaseOut1(time);
//    // interpolate position
//    id position = [self interpolateFromValue:self.fromValue toValue:self.toValue time:time];
//    // move ball view to new position
//    self.ballImageView.center = [position CGPointValue];
//    // stop the timer if we're reached the end of the animation
//    if (self.timeOffset >= self.duration) {
//        [self.timer invalidate];
//        self.timer = nil;
//    }
    // CADisplayLink
    // calculate time delta
    CFTimeInterval thisStep = CACurrentMediaTime();
    CFTimeInterval stepDuration = thisStep - self.lastStep;
    self.lastStep = thisStep;
    
    // update time offset
    self.timeOffset = MIN(self.timeOffset + stepDuration, self.duration);
    // get normalized time offset (in range 0 - 1)
    float time = self.timeOffset / self.duration;
    // apply easing
    time = bounceEaseOut1(time);
    // interpolate position
    id position = [self interpolateFromValue:self.fromValue toValue:self.toValue time:time];
    
    // move ball view to new position
    self.ballImageView.center = [position CGPointValue];
    // stop the timer if we've reached the end of the animation
    if (self.timeOffset >= self.duration) {
        [self.displayLink invalidate];
        self.displayLink = nil;
    }
}

@end
